use fxhash::FxHashMap;


use crate::gpu::program::assets::ShaderLib;

use super::{pre_processor::PreProcessor, StringUtil};

#[derive(Default)]
pub struct MorePassShader {
    pub name: String,
    pub pass_map: FxHashMap<String, Vec<PassShader>>,
}

#[derive(Default)]
pub struct PassShader {
    pub pass_type: String,
    pub shader_state: FxHashMap<String, String>,
    pub vertex_shader: String,
    pub fragment_shader: String,
}

enum Value {
    Number(i32),
    Boolean(bool),
    String(String),
}

impl From<&str> for Value {
    fn from(str: &str) -> Self {
        let str:&str = &str.to_lowercase();

        if str.starts_with('"') {
            return Self::String(str[1..(str.len() - 1)].to_owned());
        }
        
        match str {
            "true" => Value::Boolean(true),
            "false" => Value::Boolean(false),
            _ => Value::Number(str.parse::<i32>().unwrap()),
        }
    }
}

pub struct MorePassParser;

impl MorePassParser {
    pub const PASS_KEYWORD: &'static str = "pass";
    pub const SHADER_KEYWORD: &'static str = "Shader";
    pub const VERTEX_KEYWORD: &'static str = "vertex";
    pub const FRAGMENT_KEYWORD: &'static str = "fragment";
    pub const PASSTYPE_KEYWORD: &'static str = "PassType";

    pub fn parser(
        shader_lib: &ShaderLib,
        code: &str,
        define_value: &mut FxHashMap<String, String>,
    ) -> MorePassShader {
        let code = PreProcessor::filter_comment(code);
        let mut result = MorePassShader::default();
        let idx1 = code.find(Self::SHADER_KEYWORD).unwrap_or_default();
        let idx2 = StringUtil::str_find_at(&code, "{", idx1).unwrap();
        let block = code[(idx1 + Self::SHADER_KEYWORD.len())..idx2].trim();
        result.name = block[1..(block.len() - 1)].to_owned();
        let block = &code[(code.find('{').unwrap() + 1)..code.rfind('}').unwrap()];
        let pass_blocks = Self::split_pass_block(block);
     
        for pass_code in pass_blocks {
            let mut pass_shader = Self::parser_pass_block(&pass_code);

            let pass_shader_list =
                if let Some(map) = result.pass_map.get_mut(&pass_shader.pass_type) {
                    map
                } else {
                    result
                        .pass_map
                        .insert(pass_shader.pass_type.clone(), vec![]);
                    result.pass_map.get_mut(&pass_shader.pass_type).unwrap()
                };

            if pass_shader.vertex_shader.len() > 0 {
                pass_shader.vertex_shader =
                    PreProcessor::parse(shader_lib, &pass_shader.vertex_shader, define_value);
            }

            if pass_shader.fragment_shader.len() > 0 {
                pass_shader.fragment_shader =
                    PreProcessor::parse(shader_lib, &pass_shader.fragment_shader, define_value);
            }

            pass_shader_list.push(pass_shader);
        }

        result
    }

    pub fn split_pass_block(code: &str) -> Vec<String> {
        let mut offset = 0;
        let mut result = vec![];
        let len = code.len();
        while offset < len {
            if let Some(index) = StringUtil::str_find_at(code, Self::PASS_KEYWORD, offset) {
                if offset != 0 {
                    result.push(code[offset..index].to_owned());
                }
                offset = index + Self::PASS_KEYWORD.len();
            } else {
                result.push(code[offset..].to_owned());
                break;
            }
        }

        result
    }

    pub fn parser_pass_block(code: &str) -> PassShader {
        let mut pass_shader = PassShader::default();
        // let index1 = code.find(Self::PASSTYPE_KEYWORD).unwrap_or_default();
        // let index2 = StringUtil::str_find_at(code, '"', index1).unwrap_or_default();
        // let index1 = StringUtil::str_find_at(code, '"', index2 + 1).unwrap_or_default();
        // pass_shader.pass_type = code[(index1 + 1)..index2].trim().to_owned();

        Self::parser_shader_state(&mut pass_shader, code);

        if let Some(index1) = code.find(Self::VERTEX_KEYWORD) {
            pass_shader.vertex_shader =
                Self::extract_block(&code[(index1 + Self::VERTEX_KEYWORD.len())..], '{', '}');
        }
        if let Some(index1) = code.find(Self::FRAGMENT_KEYWORD) {
            pass_shader.fragment_shader =
                Self::extract_block(&code[(index1 + Self::FRAGMENT_KEYWORD.len())..], '{', '}');
        }

        pass_shader
    }

    pub fn parser_shader_state(pass_shader: &mut PassShader, code: &str) -> bool {
        if let Some(index_l) = code.find("ShaderState") {
            let index_l = StringUtil::str_find_at(code, "{", index_l).unwrap();
            let index_r = StringUtil::str_find_at(code, "}", index_l).unwrap();
            let code_block = &code[(index_l + 1)..index_r];
            let fields = code_block.split(",");
            for field in fields {
                let key_value = field.split(':').collect::<Vec<_>>();
                let key = key_value[0].trim().to_owned();
                let value = key_value[1].trim().to_owned();
                pass_shader.shader_state.insert(key, value);
            }
            true
        } else {
            false
        }
    }

    pub fn extract_block(str: &str, left: char, right: char) -> String {
        if let Some(index_l) = str.find(left) {
            let mut depth = 0;
            let mut index_r = 0;
            let str = &str[index_l..];
            for chr in str.chars() {
                if chr == left {
                    depth += 1;
                } else if chr == right {
                    depth -= 1;
                }
                if depth <= 0 {
                    break;
                }
                index_r += 1;
            }
            str[1..index_r].trim().to_owned()
        } else {
            "".to_owned()
        }
    }
}

